﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace SharpIM.Http
{
    public class HttpApiClassDescriptor
    {
        public HttpApiClassDescriptor(Type apiClass)
        {
            this.ApiClassType = apiClass;
            var attrs = apiClass.GetCustomAttributes(typeof(RoutePathAttribute), true);
            if (attrs.Any())
            {
                RoutePathValue = (attrs[0] as RoutePathAttribute).Value;
            }
            else
            {
                RoutePathValue = apiClass.Name.Substring(0, apiClass.Name.Length - 3);//remove tail 'Api'
            }
            if (!RoutePathValue.StartsWith("/"))
                RoutePathValue = "/" + RoutePathValue;

            var methods = apiClass.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            foreach (var method in methods)
            {
                var m = new HttpApiMethodDescriptor(this, method);
                this.Methods.Add(m.FullRoutePath, m);

            }
        }
        public Type ApiClassType { get; set; }
        public Dictionary<string, HttpApiMethodDescriptor> Methods = new Dictionary<string, HttpApiMethodDescriptor>();
        public string RoutePathValue { get; set; }
        internal bool IsRoutePathMatch(HttpRequest request, out HttpApiMethodDescriptor m)
        {
            m = null;
            var route = request.Route;
            if (route == null)
                return false;
            if (route.FullRoutePath.StartsWith(this.RoutePathValue, StringComparison.OrdinalIgnoreCase))
            {
                foreach (var item in Methods.Keys)
                {
                    if (Methods[item].FullRoutePath.Equals(request.Route.FullRoutePath, StringComparison.OrdinalIgnoreCase))
                    {
                        m = Methods[item];
                        return true;
                    }
                }

            }
            return false;
        }
    }
    public class HttpApiMethodDescriptor
    {
        public HttpApiMethodDescriptor(HttpApiClassDescriptor cDescriptor, MethodInfo method)
        {
            this.ClassDescriptor = cDescriptor;
            Method = method;
            this.ReturnType = method.ReturnType;
            this.Parameters = method.GetParameters();
            ParameterLength = Parameters.Length;
            var attrs = method.GetCustomAttributes(typeof(RoutePathAttribute), true);
            if (attrs.Any())
            {
                RoutePathValue = (attrs[0] as RoutePathAttribute).Value;
            }
            else
            {
                RoutePathValue = method.Name;
            }
            var routePath = cDescriptor.RoutePathValue + "/" + this.RoutePathValue;
            this.FullRoutePath = RequestPath.CombinePaths(RequestPath.GetPaths(routePath));
        }
        public string RoutePathValue { get; set; }
        public string FullRoutePath { get; set; }
        public int RequriedParameterLength { get; set; }
        public int ParameterLength { get; set; }
        public HttpApiClassDescriptor ClassDescriptor { get; set; }
        public HttpApi Class { get; set; }
        public MethodInfo Method { get; set; }
        public ParameterInfo[] Parameters { get; set; }
        public Type ReturnType { get; internal set; }

        public object Execute(HttpRequest request)
        {
            if (Class == null)
            {
                Class = Activator.CreateInstance(this.ClassDescriptor.ApiClassType) as HttpApi;
                Class.HttpContext = request.HttpContext;
            }
            try
            {
                object[] parameters = new object[ParameterLength];
                for (int i = 0; i < ParameterLength; i++)
                {
                    parameters[i] = GetParameterValue(Parameters[i].Name, Parameters[i].ParameterType, request);
                }
                return Method.Invoke(Class, parameters);
            }
            finally
            {
                (Class as IDisposable).Dispose();
            }
        }
        internal object GetParameterValueFromPayload(string name, Type type, HttpRequest request)
        {
            if (request.Payload != null)
            {
                if (type.IsClass && type != typeof(string) && !type.IsPrimitive && !type.IsValueType)
                {
                    var obj = Activator.CreateInstance(type);
                    var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                    var payloadProperties = request.Payload.Properties;
                    if (props
                        .Select(x => x.Name.ToLower())
                        .OrderBy(x => x)
                        .SequenceEqual(payloadProperties
                            .Select(x => x.ToLower())
                            .OrderBy(x => x)))
                    {
                        foreach (var item in props)
                        {
                            var v = request.Payload.ReadProperty(item.Name, item.PropertyType);
                            if (v != null)
                            {
                                item.SetValue(obj, v, null);
                            }
                        }
                        return obj;
                    }
                    return null;
                }
                else
                {
                    var obj = request.Payload.ReadProperty(name, type);
                    return obj;
                }
            }
            else
            {
                return null;
            }
        }
        internal object GetParameterValue(string name, Type type, HttpRequest request)
        {
            var value = request.QueryParameters.GetString(name.ToLower());
            if (string.IsNullOrEmpty(value) && request.Payload == null)
            {
                return null;
            }
            if (!string.IsNullOrEmpty(value))
            {
                var typeCode = Type.GetTypeCode(type);
                switch (typeCode)
                {
                    case TypeCode.Empty:
                        break;
                    case TypeCode.Object:
                        {
                            var d = GetParameterValueFromPayload(name, type, request);
                            return d;
                        }
                    case TypeCode.DBNull:
                        break;
                    case TypeCode.Boolean:
                        return Boolean.Parse(value);
                    case TypeCode.Char:
                        return Char.Parse(value);
                    case TypeCode.SByte:
                        return SByte.Parse(value);
                    case TypeCode.Byte:
                        return Byte.Parse(value);
                    case TypeCode.Int16:
                        return Int16.Parse(value);
                    case TypeCode.UInt16:
                        return UInt16.Parse(value);
                    case TypeCode.Int32:
                        return Int32.Parse(value);
                    case TypeCode.UInt32:
                        return UInt32.Parse(value);
                    case TypeCode.Int64:
                        return Int64.Parse(value);
                    case TypeCode.UInt64:
                        return UInt64.Parse(value);
                    case TypeCode.Single:
                        return Single.Parse(value);
                    case TypeCode.Double:
                        return Double.Parse(value);
                    case TypeCode.Decimal:
                        return Decimal.Parse(value);
                    case TypeCode.DateTime:
                        return DateTime.Parse(value);
                    case TypeCode.String:
                        return value;
                    default:
                        break;
                }
                throw new NotSupportedException("不支持的参数类型转换{}");
            }
            return GetParameterValueFromPayload(name, type, request);
        }
    }
}
